מאמר קצר שמסביר על אופן הבקשה של הלקוח ואיך לנתב אותו נכון, או במילים אחרות - לעשות לו "rewrite" נכון.
רובנו מכירים את הסיטואציה הבאה:
יש לי קובץ בשם members.php, שמקבל בget אידי או כל פרמטר אחר, ובסוף נוצר לנו קומבינה כזאת:
members.php?id=10&someOtherStuff=Y
ואנחנו אומרים וואלה, לא מתאים, בואו נשנה ל: /users/{id}/{otherOptionalStuff} ואז נכנס כל העיניין של
ה.htacces, ששם אנחנו מוסיפים את הrule המתאים.
הכל טוב ויפה, אבל מה אם יש לי גם קובץ search.php, וגם thread.php, וגם verycoolcode.php ולכל קובץ
אנחנו מוסיפים rule עד שיש פאסטה ענקית בקובץ.
בשביל להמנע מזה, הפיתרון מאוד פשוט: ביצוע routing ברמת האפליקציה.
wtf is that
כאמור לסיטואציה, האנשים מאיתנו שעדיין לא עברו לפריימוורק כלשהו בסוף מוצאים את עצמם במקרים כאלה ועם .htaccess עם מליון ואחת שורות. הנושא של routing ברמת האפליקציה הוא מעביר את כל הניהול של הבקשה לקוד php מאשר לעשות הכל עם .htaccess. כל העיניין הזה מקל עלינו גם מבחינת גמישות, גם מבחינת מערכת יותר נקייה וזה די יותר אסתטי. אלה שכן עברו להשתמש בפריימוורק כלשהו, מכירים את כל העיניין בצורה של או מערכים שמנהלים את כל הנושא, או מטודות סטטיות שאנחנו קוראים לקייסים שונים, או אפילו קובץ xml/yaml, וכל מה שלנו נשאר זה ליצור את הקונטרולרים ולכתוב את הrule.
אחרי שנממש את העיניין הזה, זה כל השורות שיהיו לכם ב.htaccess:
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]
</IfModule>
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]
</IfModule>
לשם דוגמה, אם יש לנו קובץ בשם members.php, ואנחנו רוצים לבצע הפניה ש /users/{id}/{otherOptionalStuff} יפנה אותנו לקובץ הזה, אפשר לעשות בסגנון כזה:
$routes = array(
'users' => array(
'controller' => 'members.php',
'params' => '/:id/?extraStuff/?secondExtraStuff',
'valid' => array('id' => '^[0-9]+$')
)
);
'users' => array(
'controller' => 'members.php',
'params' => '/:id/?extraStuff/?secondExtraStuff',
'valid' => array('id' => '^[0-9]+$')
)
);
נראה יותר מגניב, לא?
כאן 'מימשנו' מעין ערך של ראוטר בסיסי שמקבל את הurl היפה יותר שאנחנו רוצים כמפתח למערך שמכיל את הקובץ שאנחנו עובדים מולו, איזה פרמטרים אנחנו רוצים שיהיו חובה או אופצינלים, ואפילו אפשרות לעשות וולידציה רג'קסית לאחד מהפרמטרים.
איך זה יראה מהצד של המחלקה שאחראית לכל זה?
קודם נראה שה.htaccess מפנה לנו את כל הסטרינג של הבקשה לאינדקס, ומשם אנחנו יכולים להתחיל לעשות את המניפולציות שאנחנו רוצים על אותו סטרינג בקשה שאפשר למצוא ב $_SERVER['REQUEST_URI'].
לצורך העיניין נתחיל מהוצאת הבסיס שלנו, ה'קונטרולר' שאנחנו נרצה לעבוד מולו. כשאנחנו יודעים שהבקשה שלנו מחולקת לצורה הבאה: /page/param1/param2/param3.. אנחנו יכולים להתחיל מזה שנשבור את הסטרינג הזה בסלאש ונבדוק אם קיים עמוד כזה בכלל (לצורך הדוגמה מפתח בשם 'users'), משם אנחנו נבדוק איזה פרמטרים הראוטר שלנו רוצה, ונבדוק אם הם קיימים בקישור ('/:id/?extraStuff/?secondExtraStuff').
אחרי שראינו שקיימים כאלה, נבדוק אם קיים מפתח של validation. אם כן, הבדיקה נורא פשוטה: עושים בדיקת preg_match לפי הרג'קס שהגדרנו לפרמטר, אם הוא תקין אז הכל בסדר וממשיכים. לאחר כל הבדיקות והשבירות והמניפולציות, על מנת שנשאיר את המערכת שתעבוד כמו שהיא נכניס פשוט לתוך מערך $_GET את הפרמטרים החדשים שלנו שיראה בצורה כזאת פחות או יותר:
print_r($_GET); // array('id' => 3, 'extraStuff' => 'showPosts');
אחרי שהבנו את החלק התאורטי, נעבור לקוד עצמו:
אפליקציה עם הקוד: http://phpassist.com/f0811
הקלאס של הראוטר: http://pastebin.com/VYTvMGtv
(מצטער על חוסר הקומנטים :))
כמו שאפשר לראות שם, זה PoC נורא פשוט - מקבלים סטרינג, עושים מניפולציה קטנה, בודקים שהכל תקין, וטוענים את הקובץ הנכון.
אבל אני יותר מגניב ואני משתמש בmvc
העיקרון עדיין נשאר אותו דבר. רק שבמקום שכאן תעשו require_once כמוני, תשנו קצת את הסגנון:
פרמטר ראשון ישאר הקונטרולר, פרמטר שני יהיה הaction, וכל השאר זה יהיה המידע.
אחרי זה יוצרים instance חדש של הקונטרולר שלנו, בודקים עם reflection אם המטודה קיימת ומעבירים לה את הפרמטרים ($id, $whatever) במקום השיטה של להכניס למשתנה הגלובאלי $_GET.
אז מה למדנו?
המאמר נועד להסביר את העיקרון המועד מאחורי ביצוע routing בצד של האפליקציה, על מנת לקדם אותנו שלב בפיתוח של האפליקציה שלנו ואולי בפיתוח של הפריימוורק שלנו. המבנה שתיארתי מתייחס לקונטרולים כאל קבצים בפני עצמם, אך בעתיד כשנשתמש בפריימוורק קיים כלשהו אנחנו נראה שכל עמוד כזה הוא בעצם קונטולר אמיתי, וגם נראה שם איך המערכת יודעת להתמודד מול routing בצורה יותר מגניבה וגמישה (ראה ערך הראוטינג של laravel או האמת כל פריימוורק נחמד).
דוגמאות לראוטינג בפריימוורקים:
Laravel - http://laravel.com/docs/routing
Yii - http://www.yiiframework.com/doc/guide/1.1/en/topics.url
Symfony - http://symfony.com/doc/2.0/book/routing.html
סתם קלאס לראוטינג שמישהו אחר כתב - https://github.com/samwho/PHP-Routing/
תציצו, אולי תקבלו השראה לכתיבת הrouting קלאס שלכם :)
תגובות לכתבה:
אחלה מדריך!
אני יעבור עליו בפרטני יותר מאוחר ;)
מדריך מעולה, אני בטוח שהוא יעזור להרבה אנשים. תודה :-)
מאמר נחמד על מה זה..
אבל רוב הבעיות זה לא במה זה אלא באיך בונים את זה
מאמר נחמד מאד :)
דקל מה הבעיה לבנות דבר כזה?
@liorel100 כל הקטע במדריך זה שמראים איך בונים.
רוצ שאני יגיד לך מה זה REGEX אבל לא יסביר לך בכלל איך להשתמש בזה?
זה שונה...
ואם אתה עוקץ על השאלה ששאלתי על regex לא יפה מצדך.
@liorel100 לא קשור, זה הדוגמא היחידה שהייתה לי בראש.. (אחרי זה חשבתי על PDO)